package org.eoti.math.cf;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.WeakHashMap;

/**
 * The MIT License
 * <p/>
 * Copyright (c) 2009 Malachi de AElfweald (malachid@gmail.com)
 * <p/>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * <p/>
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 * <p/>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
public class CFDecimal
extends ContinuedFraction
{
	private static final BigInteger two = BigInteger.valueOf(2);
	private static final RoundingMode mode = RoundingMode.HALF_UP;
	private int scale = 100;
	private BigDecimal delegate;
	private WeakHashMap<BigInteger,Data> map = new WeakHashMap<BigInteger,Data>();

	public CFDecimal( BigDecimal delegate)
	{
		this.delegate = delegate;
		int length = delegate.toPlainString().length();
		scale = scale * length * length;
	}

	public CFDecimal( double delegate )
	{
		this(new BigDecimal(delegate));
	}

	public CFDecimal(String delegate)
	{
		this(new BigDecimal(delegate));
	}

	public BigInteger getNumerator( BigInteger index ){
		Data data = _get(index);
		if(data == null)
			return null;

		if(data.value == null)
			return null;

		return BigInteger.ONE;
	}

	private Data _get(BigInteger index)
	{
		if(map.containsKey(index)) return map.get(index);

		// otherwise, we'll calculate a few keys (like photo bracketing)

		// Step 1: See how far back we have to go
		BigInteger idx = index;
		while(!map.containsKey(idx) && (!BigInteger.ZERO.equals(idx)))
			idx = idx.subtract(BigInteger.ONE);

		// Step 2: Determine if we never went past this for a reason
		Data data = map.get(idx);
		if(data == null && map.containsKey(idx)) return null;

		// Step 3: Figure out where we are
		BigInteger value = null;
		BigDecimal remaining = null;
		if(data == null)
		{
			value = delegate.toBigInteger();
			remaining = delegate.subtract(new BigDecimal(value));
			data = new Data(value, remaining);
			map.put(idx, data);
		}

		// Step 3: calculate from here to index+2
		value = data.value;
		remaining = data.remaining;
		while(!idx.equals(index.add(two)))
		{
			idx = idx.add(BigInteger.ONE);
			if(BigDecimal.ZERO.compareTo( remaining ) == 0)
			{
				map.put(idx, null);
				break;
			}

			remaining = BigDecimal.ONE.divide(remaining, scale, mode);
			value = remaining.toBigInteger();
			/**
			 * HACK
			 *
			 * Not sure about this bit.... we need a way to tell if we are
			 * really done or not... this hack might not be right...
			 */
			if(value.bitLength() > scale)
			{
					map.put(idx, null);
					break;
			}

			if(BigInteger.ZERO.compareTo( value ) == 0)
			{
				map.put(idx, null);
				break;
			}

			remaining = remaining.subtract(new BigDecimal(value));
			map.put(idx, new Data(value, remaining)); // should this go before idx++?
		}

		return map.get(index);
	}

	public BigInteger getDenominator( BigInteger index ){
		Data data = _get(index);
		if(data == null) return null;
		return data.value;
	}

	@Override public String toString(){
		return delegate.toString();
	}

	static class Data
	{
		protected BigInteger value;
		protected BigDecimal remaining;
		protected Data(BigInteger value, BigDecimal remaining)
		{
			this.value = value;
			this.remaining = remaining;
		}
	}
}
